from dataclasses import dataclass, field
from enum import Enum
import json
from typing import Any, Dict, Optional


class ArtifactStatus(Enum):
    """Enum representing the possible states of an artifact."""

    CREATED = "CREATED"
    PROCESSING = "PROCESSING"
    FINISHED = "FINISHED"
    FAILED = "FAILED"
    ARCHIVED = "ARCHIVED"
    STOPPED = "STOPPED"


class ContentType(Enum):
    """Enum representing the possible content types of an artifact."""

    TEXT = "text/plain"
    JSON = "application/json"
    CSV = "text/csv"
    IMAGE_SVG = "image/svg+xml"
    GRAPH = "application/graph"


@dataclass
class SourceReference:
    """Class representing a reference to the source of an artifact."""

    job_id: str
    session_id: str


@dataclass
class ArtifactMetadata:
    """Class representing metadata for an artifact."""

    version: int
    description: str = ""
    metadata_dict: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        """Post initialization."""
        if not self.description:
            self.description = ""


@dataclass
class Artifact:
    """Class representing an artifact produced by an agent.

    An artifact is a produced output from an agent's processing that
    can be stored, retrieved, and manipulated within the system.

    Attributes:
        content_type (ContentType): Content type of the artifact.
        content (Any): Serialized content of the artifact.
        source_reference (SourceReference): Reference to the original object/intent that led
            to this artifact.
        id (Optional[str]): Unique identifier for the artifact. If empty, it will be auto-generated
            by the database.
        timestamp (Optional[str]): Timestamp when the artifact was generated. If empty, it will be
            auto-generated by the database.
        status (ArtifactStatus): Current status of the artifact.
        metadata (ArtifactMetadata): Additional information about the artifact.
        handle (Optional[str]): Resource locator for accessing this artifact (similar to URL).
    """

    content_type: ContentType
    content: Any
    source_reference: SourceReference

    id: Optional[str] = None
    timestamp: Optional[str] = None
    status: ArtifactStatus = ArtifactStatus.CREATED
    metadata: ArtifactMetadata = ArtifactMetadata(version=1)
    handle: Optional[str] = None

    def serialize_content(self) -> str:
        """Serialize the content of the artifact to a string.

        Returns:
            str: The serialized content as a string
        """
        if self.content is None:
            return ""

        try:
            # handle different content types
            if self.content_type == ContentType.JSON:
                # ensure JSON is properly serialized
                if isinstance(self.content, str):
                    # if already a string, validate it's proper JSON
                    # this will raise an error if invalid
                    json.loads(self.content)
                    return self.content
                else:
                    # convert to JSON string
                    return json.dumps(self.content, ensure_ascii=False)

            elif self.content_type in [
                ContentType.TEXT,
                ContentType.CSV,
            ]:
                # ensure text content is a string
                if isinstance(self.content, str):
                    return self.content
                else:
                    return str(self.content)

            elif self.content_type == ContentType.GRAPH:
                # graph data should be serialized as JSON
                if isinstance(self.content, dict):
                    return json.dumps(self.content, ensure_ascii=False)
                elif isinstance(self.content, str):
                    # validate it's proper JSON
                    json.loads(self.content)
                    return self.content
                else:
                    # try to convert object to dict and then to JSON
                    return json.dumps(
                        self.content.__dict__ if hasattr(self.content, "__dict__") else self.content
                    )

            else:
                # default to string representation
                return str(self.content)

        except Exception as e:
            raise ValueError(
                f"Failed to serialize content of type {self.content_type}: {str(e)}"
            ) from e

    @classmethod
    def deserialize_content(cls, content_str: str, content_type: ContentType) -> Any:
        """Deserialize the content of the artifact from a string.

        Args:
            content_str (str): The serialized content as a string
            content_type (ContentType): The type of the content

        Returns:
            Any: The deserialized content in its appropriate type

        Raises:
            ValueError: If the content type is not supported or deserialization fails
        """
        if not content_str:
            return None

        try:
            # handle different content types
            if content_type == ContentType.JSON:
                return json.loads(content_str)

            if content_type in [
                ContentType.TEXT,
                ContentType.CSV,
            ]:
                # text formats are returned as is
                return content_str

            if content_type == ContentType.GRAPH:
                # graph data is deserialized from JSON
                graph_dict = json.loads(content_str)
                return graph_dict

            # default to returning the string as is
            return content_str

        except Exception as e:
            raise ValueError(
                f"Failed to deserialize content of type {content_type}: {str(e)}"
            ) from e
